<?php

declare(strict_types=1);

namespace core;

use Exception;

/**
 * 路由解析类
 *
 * 依赖 Request 对象来解析 URL 并提取路由信息：
 * - 解析 URL 路径段
 * - 规范化控制器和方法名称
 * - 提取路由参数
 */
final class Router
{
    /** @var string 默认控制器名称 */
    private string $defaultController;

    /** @var string 默认方法名称 */
    private string $defaultMethod;

    /** @var array<int, string> URL 路径段数组 */
    private array $segments = [];

    /** @var string 当前路由到的控制器名称 */
    private string $controller;

    /** @var string 当前路由到的方法名称 */
    private string $method;

    /** @var array<int, string> 路由额外参数（控制器和方法之后的路径段） */
    private array $params = [];

    /**
     * 初始化路由解析器
     *
     * @param array<string, mixed> $config 应用程序配置数组
     * @param Request $request 当前的请求对象
     *
     * @throws Exception 当 URL 解析失败时抛出异常
     */
    public function __construct(array $config, Request $request)
    {
        // 从配置中读取
        $this->defaultController = $config['default_controller'] ?? 'home';
        $this->defaultMethod     = $config['default_method'] ?? 'index';

        // 解析路径段
        $this->segments = $this->parseUrlSegments($request->getPath());

        // 提取控制器、方法、参数
        $controller = $this->segments[0] ?? $this->defaultController;
        $method     = $this->segments[1] ?? $this->defaultMethod;

        // 非法时抛 404，不再回退默认
        if (!$this->isValidName($controller)) {
            throw new Exception('Controller not found');
        }
        if (!$this->isValidName($method)) {
            throw new Exception('Method not found');
        }

        $this->controller = $controller;
        $this->method     = $method;
        $this->params     = array_slice($this->segments, 2);
    }

    /**
     * 解析当前请求 URL 并提取路径段
     *
     * 1. 规范化路径（移除多余斜杠）
     * 2. 执行安全检查（防止路径遍历和非法字符）
     * 3. 过滤无效段
     *
     * @param string $path 从 Request 对象获取的纯净 URL 路径 (例如 /users/profile/123)
     * @return array<int, string> 例如 ['users','profile','123']
     *
     * @throws Exception 当路径包含非法字符或路径遍历尝试时抛出异常
     */
    private function parseUrlSegments(string $path): array
    {
        // 1. 移除多余斜杠 + 防止路径注入
        $path = preg_replace('#/+#', '/', $path);
        $path = trim($path, '/');

        // 2. 处理空路径
        if ($path === '') {
            return [];
        }

        // 3. 分割路径
        $segments = explode('/', $path);

        // 4. 安全检查 & 过滤
        $cleanSegments = [];
        foreach ($segments as $segment) {
            // 跳过空段
            if ($segment === '') {
                continue;
            }

            // 4.1 防止路径遍历
            if (strpos($segment, '..') !== false) {
                throw new Exception("Path traversal attempt detected in segment: {$segment}");
            }

            // 4.2 验证字符集
            if (!preg_match('/^[a-zA-Z0-9_\-]+$/', $segment)) {
                // 严格模式：拒绝包含非法字符的URL
                throw new Exception("Invalid characters in path segment: {$segment}");
            }

            $cleanSegments[] = $segment;
        }

        return array_values($cleanSegments);
    }

    /**
     * 验证控制器/方法名称是否合法
     *
     * 规则：
     * - 必须以字母开头
     * - 只能包含字母、数字和下划线
     *
     * @param string $name 要验证的名称
     * @return bool 如果名称合法返回 true，否则返回 false
     */
    private function isValidName(string $name): bool
    {
        return (bool) preg_match('/^[a-zA-Z][a-zA-Z0-9_]*$/', $name);
    }

    /*-----------------------------
     * Getters
     *----------------------------*/

    /**
     * 获取控制器名称
     *
     * @return string 控制器类名（不含命名空间和后缀）
     */
    public function getControllerName(): string
    {
        return $this->controller;
    }

    /**
     * 获取方法名称
     *
     * @return string 控制器方法名
     */
    public function getMethodName(): string
    {
        return $this->method;
    }

    /**
     * 获取路由参数
     *
     * @return array<int, string> 路由额外参数数组
     */
    public function getParams(): array
    {
        return $this->params;
    }

    /**
     * 获取解析后的 URL 路径段数组
     *
     * 数组结构:
     * - 索引 0: 控制器名称
     * - 索引 1: 方法名称
     * - 索引 2+: 路由参数
     *
     * @return array<int, string> 包含 URL 路径段的索引数组
     */
    public function getSegments(): array
    {
        return $this->segments;
    }
}
